matplotlib

  • matplotlib은 그래프를 그리기 위해 사용하며 상상할 수 있는 대부분의 그래프를 그릴 수 있다.

Matplotlib makes easy things easy and hard things possible.

  • matplotlib.pyplot은 그래프를 그릴 때 사용하는 명령어를 모아놓은 것으로, MATLAB의 명령어들과 유사한 기능을 한다.
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

x = np.linspace(-10,10)
y = x**2

plt.plot(y, "g*--");

MATLAB style state-based interface

define x, y                       # input
matplotlib.pyplot.plot(x,y)       # create Line2D object
matplotlib.pyplot.show()          # renders the object
  • 입력은 np.array이므로 다른 유형은 변환하여 사용한다.
  • matplotlib.pyplot은 line plot 이외에도 scatter plot, bar chart, contour map, histogram, pie chart, scatter plot 과 같은 그림 도구를 제공한다.
  • jupyter notebook에서는 그림의 출력에 관한 몇 가지 선택을 제공한다.
    • %matplotlib inline은 결과물을 해당 code cell의 아래에 출력
    • %matplotlib notebookmatplotlib.pyplot.show()과 같은 option을 제공한다.

그림판 지정 및 설정

fig = plt.figure()
ax = plt.axes()
  • plt.plot으로 그림을 그릴 경우 figure와 axes는 암묵적으로 생성이 된다.
  • figure와 axes를 사용할 필요가 있다면 matplotlib.pyplot.gcf()matplotlib.pyplot.gca()으로 활성화 시킬 수 있다.

figure 그림판

matplotlib.pyplot.figure(num=None, figsize=None, dpi=None, 
                         facecolor=None, edgecolor=None, frameon=True, 
                         clear=False, **kwargs)
keywards default description
num None activate the referred figure
figsize 6.4x4.8, figure.figsize figure size in in inches (width, height)
dpi 100, figure.dpi resolution in dots per inch
facecolor ‘C0’, figure.facecolor color of the drawing background
edgecolor None, figure.edgecolor color of edge around the drawing background
frameon True draw figure frame or not
clear True if figure is already exists, then it is cleared

https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure

axes 그림틀

  • 그림판에 그림틀을 지정하여 그 위에 그림을 그리게 된다.
  • axes의 크기와 위치 등을 지정할 수 있다.
  • axes를 포함하여 figure 위에 그리는 모든 object들을 artists라고 한다.
plt.axes(arg=None, **kwargs)
plt.subplot(111, **kwargs)

https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.axes.html

그래프 plot

matplotlib.pyplot.plot(*args, scalex=True, scaley=True, data=None, **kwargs)
keywards default description
alpha 1 opacity, [0,1]
linestyle, ls '-' {'-', '--', '-.', ':', '', ...}
color or c line color
linewidth or lw 1 positive float
marker None marker style
label None label for legend
  • 지정할 내용이 간단할 때는 format string을 사용한다.
fmt = "[color][marker][line]"

https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html

In [2]:
line, = plt.plot(x[::5])

색, 줄 모양, 마커 모양

character color character description character description character description
‘b’ blue '-' solid line style '<' triangle_left marker 'h' hexagon1 marker
‘g’ green '--' dashed line style '>' triangle_right marker 'H' hexagon2 marker
‘r’ red '-.' dash-dot line style '1' tri_down marker '+' plus marker
‘c’ cyan ':' dotted line style '2' tri_up marker 'x' x marker
‘m’ magenta '.' point marker '3' tri_left marker 'D' diamond marker
‘y’ yellow ',' pixel marker '4' tri_right marker 'd' thin_diamond marker
‘k’ black 'o' circle marker 's' square marker ' ' vline marker
‘w’ white 'v' triangle_down marker 'p' pentagon marker '_' hline marker
'^' triangle_up marker '*' star marker
In [3]:
line = plt.plot(x[::5], marker='o', color='dimgray', markerfacecolor='lightgray', markeredgecolor="gray")

x,y 축 설정

xmin, xmax = plt.xlim( (xmin, xmax) )
locs, labels = plt.xticks(locs, labels)
plt.xscale(scale) #scale = 'linear', 'log', 'logit', 'symlog'
In [4]:
plt.plot(x[::5], marker='o', color='dimgray', markerfacecolor='lightgray', markeredgecolor="gray")
plt.xlim([-1,10])
plt.ylim([-12,11])
plt.xticks(range(11), list(range(10))+[''])
plt.grid(axis='y')
plt.title(f'plot of {len(x[::5]):d} observations', fontsize=16);
In [5]:
plt.figure(figsize=(7,5))

x = np.arange(11)

plt.plot(x**2, label=r"quadratic function, $f(x)=x^2$")
plt.plot(x**3, label=r"cubic function, $f(x)=x^3$")

plt.xlim(0,10)
plt.ylim(0,300)

plt.xlabel(r"$x$", fontsize=14)
plt.xticks([1,3,8,9], fontsize=14)

plt.ylabel(r"$f(x)$", fontsize=14)

plt.legend(fontsize=14)
plt.grid(axis = "x", which='major')

plt.suptitle("LINE PLOTS", fontsize=16)
plt.tight_layout()

Customized Axes

  • axes의 크기와 위치는 원하는대로 조정이 가능하다.
  • 격자 모양에 맞는 크기라면 plt.subplot이나 fig.add_subplot으로 어렵지 않게 구성할 수 있다.
  • 임의의 위치와 크기가 필요하다면 좀 번거롭긴 하지만 fig.add_axes를 사용할 수 있다.
In [6]:
fig = plt.figure(figsize=(5,4))
plt.subplot(221)
plt.subplot(223)
plt.subplot(122);

한글 설정

  • figure에 한글을 사용하기 위해서는 figure마다 사용할 font를 지정하거나 전체 notebook의 font를 일괄 지정할 수 있디.
  • default font를 바꾸려면 .../python3.8/site-packages/matplotlib/mpl-data/matplotlibrc을 수정해야 한다. 이곳을 참고

네이버 글꼴 모음, 나눔고딕 바로 내려받기

path 로 font를 지정하는 방법

  • 한글을 사용할 때마다 font를 지정해야 한다.
  • Colab에서 가장 간단하게 사용할 수 있는 대안이다.
In [7]:
import matplotlib.font_manager as fm

path = r"C:\Users\K5\AppData\Local\Microsoft\Windows\Fonts\NanumGothicBold.ttf"

NanumGothic = fm.FontProperties(fname = path, size=12)

plt.title('한글', fontproperties=NanumGothic, fontsize=16);

path 로 font family를 지정하는 방법

  • 한번 실행하면 이후 figure에선 모두 지정한 font를 사용한다.
  • windows에 해당 font가 설치되어 있어야 한다.
  • 한번 지정하면 이후 그림에서 동일한 font를 사용한다. Default 값을 사용하려면 다음 code를 실행
plt.style.use(['default'])
In [8]:
from matplotlib import rcParams
import matplotlib.font_manager as fm

#NanumGothic = fm.FontProperties(fname=path)
#rcParams['font.family'] = NanumGothic.get_name()

rcParams['font.family'] = 'NanumGothic'
# label에 minus sign이 제대로 표시되기 않는 경우에 삽입
rcParams["axes.unicode_minus"] = False 

plt.title('한글', fontsize=16)

plt.style.use('default')

Object-Oriented interface of matplotlib

  • 상태기반 접속방법 state-based interface
  • 객체지향적 접속방법 object-oriented interface
  • object-oriented apporach는 축을 객체로 하여 관련 method를 적용하는 방법으로 앞서 소개한 벙법보다 간편할 수 있다.
  • 두 방법은 서로 완전대체가 어려우므로 하나를 주로 사용하더라도 다른 방법의 기본 명령어 정도는 숙지하고 있어야 한다.
define x, y                       # input
figure, axes = plt.subplots()     # create class
axes.plot(x,y)                    # create Line2D object
plt.show()                        # renders the object

figure와 axes 객체 생성

fig, axes = plt.subplots(nrows=1, ncols=1, sharex=False, sharey=False, **fig_kw)
fig.add_subplot(nrows, ncols, index, **kwargs)
fig.add_axes([left, bottom, width, height], **kwargs)
In [9]:
fig = plt.figure(figsize=(8,6))
fig.add_subplot(221)
plt.plot(x**2)
fig.add_subplot(223)
plt.plot(x**3)
fig.add_subplot(122)
fig.add_axes([.2,.2,.5,.5]);

꾸미기

  • ax.plot()은 객체 ax에 method로 작용한다.
  • Objected Oriented API에선 customize를 위한 모든 추가 작업을 method로 처리할 수 있다.
  • plot을 변수로 지정하여 사용하기도 한다.
line, = ax.plot(sample_size, var_of_sample_mean, "b")
line.set_label(r"variance of $\overline{X}$")
  • 대부분의 명령어들은 plt.xlimax.set_xlim와 같은 형태로 비슷하게 사용된다.

x,y축의 범위와 눈금 설정

xmin, xmax = np.min(sample), np.max(sample)
x_range = xmax - xmin
x_margin = 0.1*x_range

ax.set_xlim(xmin-x_margin, xmax+x_margin)

ax.set_xticks(sample[::3])
ax.set_xticklabels(sample[::3])

legend 범례

  • 객체에 label keyword로 label을 지정하고 그리거나
  • 객체의 순서에 따라 label을 별도로 지정할 수 있다.

text 삽입

Text

ax.text(x, y, s)
  • 위치를 지정할 때는 자료의 좌표가 기본이 된다.
  • Keyword argument transform=ax.transAxes로 axes의 상대적인 위치를 사용할 수 있다.
    [0,1] 사이의 값으로 axes 왼쪽 아래부분이 좌표의 기준이다.

Annotation

pyton
ax.annotation()
  • 화살표가 달린 설명이다. text와 유사한 방법으로 사용한다. xy는 화살표의 위치, xytext는 text의 위치를 지정한다.
    pytnon
    ax.annotate(text, xy, xytext, *args, **kwargs)
In [10]:
data = pd.read_csv("https://github.com/k5yi/econ2005/blob/master/datasets/teen_birth.txt?raw=True", sep="\t")

random_sampling = np.random.RandomState(1).randint(1, len(data), size = 8)
data = data.loc[random_sampling]

x = data.PovPct.to_numpy()
y = data.Brth15to17.to_numpy()
state = data.Location.to_numpy()

slope, intercept = np.polyfit(x, y, deg=1)
f = lambda x: intercept + slope*x

x_unit = (np.max(x) - np.min(x))/50
y_unit = (np.max(y) - np.min(y))/50

xlim=(np.min(x) - 10*x_unit, np.max(x) + 10*x_unit)
ylim=(np.min(y) - 10*y_unit, np.max(y) + 10*y_unit)



fig, ax = plt.subplots(figsize=(8,6))

ax.plot(xlim, [f(x) for x in xlim], c='gray', lw=2, label="fitted line")
ax.scatter(x, y, ec='dimgray', c="lightgray", s=20, label="sample")            

for i in range(len(y)):
        
    # data point to the fitted line
    label="residual/error" if i == 1 else ""
    ax.plot([x[i],x[i]], [y[i], f(x[i])], "r-", lw = 1.2, label=label)
        
    if y[i] >= f(x[i]):
        ax.annotate(state[i], xy=(x[i] - x_unit, y[i] + y_unit), fontsize=12)
    else: ax.annotate(state[i], xy=(x[i] - x_unit,y[i] - 3*y_unit), fontsize=12)

ax.set(xlim=xlim, ylim=ylim)
ax.axhline(y = np.mean(y), c="lightgray", lw=1, label=r"mean of $y$")
ax.set_xlabel("Poverty Rate", fontsize=14)
ax.set_ylabel("Birth Rate of Age 15-17", fontsize=14)
ax.legend(fontsize=14, scatterpoints=3)
fig.suptitle("Deviations", fontsize=16)
fig.tight_layout()

사전 정의된 그림 형식

  • default: plt.rcdefaults()
  • 스타일 사용: plt.style.use()
  • 지원 스타일 확인: plt.style.available
  • skectch: plt.xkcd()
In [11]:
import seaborn as sns

plt.style.use('ggplot')
plt.plot(x);

sns.reset_orig()
In [12]:
fig, axes = plt.subplots(1, 2, figsize=(8,4))

with plt.xkcd():
    axes[0].plot(x)
    axes[1].plot(y)

fig.tight_layout()

Central limit theorem - 이항분포함수

  • 표본수를 증가시키면서 이항분포를 따르는 표본의 평균과 분산이 어떻게 변하는지 그림으로 그려보자.
  1. $p = 0.3$을 사용하고 $n$은 5부터 100까지 24개의 단계로 나누어 표본 평균과 표본 분산을 계산한다.
$$ \bar{x} = \frac{\sum^n_{i=1} x_i}{n}, \quad\quad \hat{\sigma}^2 = \frac{1}{n-1} \sum^n_{i=1} (x_i - \bar{x}) $$

2-1. 평균과 분산은 confidence interval을 이용하여 표시한다. 2-2. x축을 0과 1로 정규화한 후 빈도를 bin의 수가 12개인 histogram으로 그려본다.

  • 고려할 점 n이 작을때는 평균과 분산이 크게 변하지만 n이 커지면서 그 크기가 줄어든다. 표본 수를 log 축을 기준으로 선택하고 x축을 log로 표시하자.
In [13]:
sample_sizes = np.logspace(.75, 3, 24).astype(int)
print(sample_sizes)
[   5    7    8   11   13   17   21   27   34   42   53   67   83  105
  131  164  206  258  324  406  508  637  798 1000]
  • Binomial distribution의 표본분산은 $n \hat{p}(1-\hat{p})$로 계산할 수 있지만
    여기선 표본을 20번의 추출하여 계산한 평균의 분산을 계산하여 사용한다.
In [14]:
def mean_variance(n, p=.3, resampling=20):
    means = np.mean(np.random.RandomState(42).choice([0,1], (resampling, n), p=(1-p,p)), axis=-1)
    mean = np.mean(means)
    std = np.std(means)
    return mean, std

mean = np.array([])
std = np.array([])

for i in sample_sizes:
    m, s = mean_variance(i, p=.3)
    mean = np.append(mean, m)
    std = np.append(std, s)
In [15]:
fig, ax = plt.subplots(figsize=(8,6))

ax.plot(sample_sizes, mean+1.96*std)
ax.plot(sample_sizes, np.maximum(0,mean-1.96*std))
ax.plot(sample_sizes, mean);
In [16]:
fig, ax = plt.subplots(figsize=(8,6))

ax.plot(sample_sizes, mean+1.96*std, color="gray")
ax.plot(sample_sizes, np.maximum(0,mean-1.96*std), color="gray")
ax.fill_between(sample_sizes, np.maximum(0,mean-1.96*std), mean+1.96*std, color='lightgray')

ax.plot(sample_sizes, mean, color = "dimgray")
ax.set_xscale('log')
  • 각 축의 limit과 tick, ticklabel을 정리하고 각 plot에 label을 붙여주자.
In [17]:
fig, ax = plt.subplots(figsize=(8,6))

ax.plot(sample_sizes, mean, color="dimgray", label='Sample Mean')

ax.plot(sample_sizes, mean+1.96*std, color="gray")
ax.plot(sample_sizes, np.maximum(0,mean-1.96*std), color="gray")
ax.fill_between(sample_sizes, np.maximum(0,mean-1.96*std), mean+1.96*std, 
                color='lightgray', label=r'95% confidence interval')

ax.set_xscale('log')

ax.set(ylim=[-0.1, .8], yticks = np.arange(0,.71,.1))
ax.legend(fontsize=14)
ax.grid(which="both")

fig.suptitle('Figure 1. Sample Mean and Variance of Sample Mean', fontsize=16)
fig.tight_layout();

Plot Types

  1. bar chart, stacked bar chart, grouped bar chart 또는 clustered bar chart
    • Category 변수에 대응하는 값을 비교
    • matplotlib으로 stacked bar chart나 grouped bar chart를 만드는 것은 많은 연습이 필요하다.
    • Pandas로 쉽게 만들 수 있다.
  1. histogram, kernel density
    • 연속변수의 빈도수를 discete하게 표현한다.
    • kernel density 역시 Pandas에서 간단히 만들 수 있는 option이 있다.
  1. pie chart
    • 비율을 비교할때 bar chart와 함께 많이 사용한다.
    • 절대적인 크기의 차이를 파악하기 어렵다.
    • 보통 자료 전체를 한눈에 쉽게 보기 위해 사용한다.
  1. box plot
    • 각 category의 전체 분포를 한눈에 파악할 수 있다는 장점이 있다.

https://matplotlib.org/stable/plot_types/

Animation

여러개의 그래프를 순서대로 보여주는 것

!conda install -c conda-forge ffmpeg
In [18]:
from scipy.stats import binom

n, p = 100, 0.3
mean, var, skew, kurt = binom.stats(n, p, moments='mvsk')

fig, ax = plt.subplots(figsize=(8,6))

x = np.arange(binom.ppf(0.00001, n, p),
              binom.ppf(0.99999, n, p))

ax.plot(x/n, binom.pmf(x, n, p), color='dimgray', label='binom pmf')
ax.set_xlim([-0.1, .8])


y_min = 0
y_max = binom.pmf(mean, n, p)
y_range = y_max - y_min

ylim = [y_min - 0.1*y_range, y_max + 0.1*y_range]
ax.set_ylim(*ylim)

p_hat = r'$\hat{p}$'
ax.axhline(0, color='lightgray')
#ax.axvline(x=mean/n, ymin=0, ymax=binom.pmf(mean, n, p), color='lightgray')
ax.set_title(f'Binomial Distibution: n = {n:d}, {p_hat:s} = {mean/n:.2f}', fontsize=16)

plt.tight_layout()
In [19]:
from matplotlib.animation import FuncAnimation
from IPython.display import HTML  

fig, ax = plt.subplots(figsize=(8,6))

def binom_density(n, p=0.3):    
    
    mean = binom.stats(n, p, moments='m')
    
    x = np.arange(binom.ppf(0.00001, n, p),
                  binom.ppf(0.99999, n, p))
    
    ax.clear()
    
    ax.plot(x/n, binom.pmf(x, n, p), color='dimgray', ms=8, label='binom pmf')
    
    ax.set_xlim([-0.1, .8])
    ax.set_ylim([0, 1.1*np.max(binom.pmf(x, n, p))])
    
    p_hat = r'$\hat{p}$'
    ax.axhline(0, color='lightgray')
    ax.set_title(f'Binomial Distibution: n = {n:d}, {p_hat:s} = {mean/n:.2f}', fontsize=16)
    
    return line
    
animator = FuncAnimation(fig, binom_density, frames=range(2, 100, 10), interval=500)

#HTML(animator.to_html5_video())
HTML(animator.to_jshtml())
Out[19]:
def live_data(year): df_year = df[df.year==year].sort_values('value', ascending=False).head(10) ax.clear() ax.barh(df_year['name'], df_year['value'], color=[group_color[city_group(city)] for city in df_year['name']]) dx = df_year['value'].max()/200 for i, (value, name) in enumerate(zip(df_year['value'], df_year['name'])): ax.text(value-dx, i, name, size=12, weight=600, ha='right, va='bottom') ax.text(value-dx, i-.24, city_group[name], size=12, weight=600, ha='right, va='baseline') ax.text(vlaue+dx, i, f'{vlaue:,.0f}', size=12, ha='left', va='center') ax.text(1, 0.4, year, transform=ax.transAxes, color='#77777', size=46, ha='right', weight=800) ax.text(0, 1.064, 'population (thousand)', transform=ax.transAxes, color='#77777', size=12) ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}')) ax.xaxis.set_ticks_position('top') ax.tick_params(axis='x', colors='#77777', labelsize=12) ax.set_yticks([]) ax.grid(which="major", axis='x', linestyle='-') ax.set_axisbelow(True) ax.text(0, 1.12, 'the most ...', transform=ax.transAxes, color='#77777', size=24, ha='left', weight=600) plt.box(False) animator = animation.FuncAnimation(fig, chart_race, frames=range(1979, 2012, 1)) from IPython.display import HTML HTML(animator.to_jshtml())

seaborn과 plotly

  • 상당한 수의 유용한 wrapper들을 같이 제공하므로 정형화된 그림을 그릴 때는 좋은 선택이 될 수 있다.
In [20]:
import seaborn as sns

x = np.random.RandomState(42).randn(3,3)
corr = np.corrcoef(x)

sns.set(font_scale=1.4)
sns.heatmap(corr, annot=True);
In [21]:
import plotly.express as px

df = px.data.iris()
px.scatter_3d(df, x='sepal_length', y='sepal_width', z='petal_width', color='species')